package com.uinnova.product.eam.service.es;

import com.alibaba.fastjson.JSON;
import com.binary.core.util.BinaryUtils;
import com.binary.jdbc.Page;
import com.uinnova.product.vmdb.comm.model.rlt.CCcCiRlt;
import com.uino.bean.cmdb.base.ESCIRltInfo;
import com.uino.bean.cmdb.base.ESCIRltInfoHistory;
import com.uino.dao.AbstractESBaseDao;
import com.uino.dao.ESConst;
import com.uino.dao.cmdb.ESCIRltInfoHistorySvc;
import com.uino.util.sys.BeanUtil;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

@Repository
public class IamsESCIRltInfoHistoryDesignSvc extends AbstractESBaseDao<ESCIRltInfoHistory, CCcCiRlt> {

    public static Logger log = LoggerFactory.getLogger(IamsESCIRltInfoHistoryDesignSvc.class);

    @Override
    public String getIndex() {
        // TODO Auto-generated method stub
        return ESConst.INDEX_CMDB_CIRLT_HIS + "_design";
    }

    @Override
    public String getType() {
        // TODO Auto-generated method stub
        return ESConst.INDEX_CMDB_CIRLT_HIS + "_design";
    }


    @Autowired
    @Lazy
    private IamsESCIRltDesignSvc ciRltSvc;

    @PostConstruct
    public void init() {
        super.initIndex();
        // List<ESCIRltInfoHistory> hiss =
        // BeanUtil.converBean(ciRltSvc.getListByQuery(QueryBuilders.boolQuery()),
        // ESCIRltInfoHistory.class);
        // hiss.forEach(his -> {
        // his.setRltId(his.getId());
        // his.setId(null);
        // his.setVersion(1L);
        // });
        // super.initIndex(hiss);
    }

    /**
     * 获取{rltId：maxVersion}键值对
     *
     * @param rltIds
     *            if null:获取全部rltid：maxversion键值对 ，if notnull:仅获取指定rltid
     * @return {rltid：maxVersion}
     */
    private Map<Long, Long> getRltIdMaxVersionCore(Collection<Long> rltIds) {
        Map<Long, Long> res = new HashMap<>();
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        Map<String, BigDecimal> rltIdMaxVersionMap = super.groupByFieldMaxVal("rltId", "version", query);
        if (rltIdMaxVersionMap != null && rltIdMaxVersionMap.size() > 0) {
            rltIdMaxVersionMap.forEach((key, val) -> res.put(Long.valueOf(key), val.longValue()));
        }
        return res;
    }

    /**
     * @see ESCIRltInfoHistorySvc(Collection)
     * @return
     */
    public Map<Long, Long> getRltIdMaxVersion(Collection<Long> rltIds) {
        return this.getRltIdMaxVersionCore(rltIds);
    }

    /**
     * 获取指定rltid得maxversion
     *
     * @param rltId
     * @return maxversion
     */
    public Long getRltIdMaxVersion(Long rltId) {
        Map<Long, Long> resMap = this.getRltIdMaxVersionCore(Collections.singletonList(rltId));
        if (resMap == null || resMap.size() <= 0) {
            return 0L;
        } else {
            return resMap.get(rltId);
        }
    }

    private static final Lock lock = new ReentrantLock();

    /**
     * @see ESCIRltInfoHistorySvc#saveCIRltHistoryByCIRlts(Collection)
     * @param rlts
     */
    public void saveCIRltHistoryByCIRlts(ESCIRltInfo... rlts) {
        saveCIRltHistoryByCIRlts(Arrays.asList(rlts));
    }

    /**
     * 根据关系查询条件和操作动作保存关系历史
     *
     * @param rltQuery
     *            关系查询条件
     * @param actionType
     *            操作动作
     */
    public void saveCIRltHistoryByRltQuery(QueryBuilder rltQuery, ESCIRltInfoHistorySvc.ActionType actionType) {
        long writeHisRltNum = ciRltSvc.countByCondition(rltQuery);
        if (writeHisRltNum > 0) {
            List<ESCIRltInfo> writeHisRlts = ciRltSvc
                    .getListByQuery(1, new BigDecimal(writeHisRltNum).intValue(), rltQuery).getData();
            saveCIRltHistoryByCIRlts(writeHisRlts, actionType);
        }
    }

    /**
     * @see ESCIRltInfoHistorySvc#saveCIRltHistoryByCIRlts(Collection,
     *      ESCIRltInfoHistorySvc.ActionType)
     * @param rlts
     */
    public void saveCIRltHistoryByCIRlts(Collection<ESCIRltInfo> rlts) {
        saveCIRltHistoryByCIRlts(rlts, ESCIRltInfoHistorySvc.ActionType.SAVE_OR_UPDATE, false);
    }

    /**
     * 根据ci关系信息生成关系历史信息
     *
     * @param rlts
     * @param actionType
     */
    public void saveCIRltHistoryByCIRlts(Collection<ESCIRltInfo> rlts, ESCIRltInfoHistorySvc.ActionType actionType) {
        new Thread(new Runnable() {

            ESCIRltInfoHistorySvc.ActionType type = actionType == null ? ESCIRltInfoHistorySvc.ActionType.SAVE_OR_UPDATE : actionType;

            /**
             * 获取指定uniqueCode得下一个version并更新至uniqueCodeMaxVersionMap中
             *
             * @param uniqueCodeMaxVersionMap
             * @param rltId
             * @return
             */
            private Long getNextVersion(Map<Long, Long> uniqueCodeMaxVersionMap, Long rltId) {
                if (uniqueCodeMaxVersionMap.get(rltId) == null) {
                    uniqueCodeMaxVersionMap.put(rltId, 1L);
                } else {
                    uniqueCodeMaxVersionMap.put(rltId, uniqueCodeMaxVersionMap.get(rltId) + 1L);
                }
                return uniqueCodeMaxVersionMap.get(rltId);
            }

            public void run() {
                try {
                    lock.lock();
                    if (rlts == null || rlts.size() <= 0) { return; }
                    // 本次保存历史所有关系数据得ids
                    Set<Long> rltIds = rlts.stream().collect(Collectors.groupingBy(ESCIRltInfo::getId)).keySet();
                    // rltid:maxversion键值对
                    Map<Long, Long> rltIdMaxVersionMap = getRltIdMaxVersion(rltIds);
                    // 需要生成得关系历史
                    List<ESCIRltInfoHistory> rltHistorys = BeanUtil.converBean(rlts, ESCIRltInfoHistory.class);
                    rltHistorys.forEach(history -> {
                        history.setRltId(history.getId());
                        history.setId(null);
                    });
                    // 如果是保存动作判断一下与上个版本是否有差异，刨除无差异的,只处理刨除后的数据
                    removeNochangeRltHis(rltHistorys);
                    if (rltHistorys == null || rltHistorys.size() <= 0) { return; }
                    // 填补关系历史版本
                    rltHistorys.forEach(rltHistory -> {
                        Long rltId = rltHistory.getRltId();
                        Long version = getNextVersion(rltIdMaxVersionMap, rltId);
                        rltHistory.setVersion(version);
                        rltHistory.setAction(type.getName());
                    });
                    saveOrUpdateBatchNoRefresh(rltHistorys);
                } catch (Exception e) {
                    log.error("保存关系历史异常，对应关系数据【{}】，对应异常信息【{}】", JSON.toJSONString(rlts), e);
                } finally {
                    lock.unlock();
                }
            }

            /**
             * 移除未变更的历史数据
             *
             * @param rlts
             * @param
             */
            private void removeNochangeRltHis(List<ESCIRltInfoHistory> rlts) {
                Set<Long> rltIds = rlts.stream().collect(Collectors.groupingBy(ESCIRltInfoHistory::getRltId)).keySet();
                Page<ESCIRltInfoHistory> page = getListByQuery(1, 1, QueryBuilders.boolQuery());
                if (!BinaryUtils.isEmpty(page.getData())) {
                    // 按照rltId取max折叠查
                    List<ESCIRltInfoHistory> maxHiss = getListNoRepeat(rltIds.size(),
                            QueryBuilders.termsQuery("rltId", rltIds), "rltId", "version", false);
                    if (maxHiss != null && maxHiss.size() > 0) {
                        Map<Long, List<ESCIRltInfoHistory>> rltIdMaxHisMap = maxHiss.stream()
                                .collect(Collectors.groupingBy(ESCIRltInfoHistory::getRltId));
                        rlts.removeIf(readRltHis -> {
                            Long rltId = readRltHis.getRltId();
                            if (rltIdMaxHisMap.containsKey(rltId)) {
                                ESCIRltInfoHistory rltMaxHis = rltIdMaxHisMap.get(rltId).get(0);
                                return readRltHis.equalsInfo(rltMaxHis);
                            }
                            return false;
                        });
                    }
                }
            }
        }).start();
    }


    /**
     * 根据ci关系信息生成关系历史信息 支持异步/同步
     *
     * @param rlts
     * @param actionType
     */
    public void saveCIRltHistoryByCIRlts(Collection<ESCIRltInfo> rlts, ESCIRltInfoHistorySvc.ActionType actionType, Boolean isAsync) {
        if (isAsync) {
            this.saveCIRltHistoryByCIRlts(rlts, actionType);
        } else {
            // 同步处理
            ESCIRltInfoHistorySvc.ActionType type = actionType == null ? ESCIRltInfoHistorySvc.ActionType.SAVE_OR_UPDATE : actionType;

            try {
                lock.lock();
                if (rlts == null || rlts.size() <= 0) { return; }
                // 本次保存历史所有关系数据得ids
                Set<Long> rltIds = rlts.stream().collect(Collectors.groupingBy(ESCIRltInfo::getId)).keySet();
                // rltid:maxversion键值对
                Map<Long, Long> rltIdMaxVersionMap = getRltIdMaxVersion(rltIds);
                // 需要生成得关系历史
                List<ESCIRltInfoHistory> rltHistorys = BeanUtil.converBean(rlts, ESCIRltInfoHistory.class);
                rltHistorys.forEach(history -> {
                    history.setRltId(history.getId());
                    history.setId(null);
                });
                // 如果是保存动作判断一下与上个版本是否有差异，刨除无差异的,只处理刨除后的数据
                removeNochangeRltHis(rltHistorys);
                if (rltHistorys == null || rltHistorys.size() <= 0) { return; }
                // 填补关系历史版本
                rltHistorys.forEach(rltHistory -> {
                    Long rltId = rltHistory.getRltId();
                    Long version = getNextVersion(rltIdMaxVersionMap, rltId);
                    rltHistory.setVersion(version);
                    rltHistory.setAction(type.getName());
                });
                saveOrUpdateBatch(rltHistorys);
            } catch (Exception e) {
                log.error("保存关系历史异常，对应关系数据【{}】，对应异常信息【{}】", JSON.toJSONString(rlts), e);
            } finally {
                lock.unlock();
            }


        }
    }

    /**
     * 获取指定uniqueCode得下一个version并更新至uniqueCodeMaxVersionMap中
     *
     * @param uniqueCodeMaxVersionMap
     * @param rltId
     * @return
     */
    private Long getNextVersion(Map<Long, Long> uniqueCodeMaxVersionMap, Long rltId) {
        if (uniqueCodeMaxVersionMap.get(rltId) == null) {
            uniqueCodeMaxVersionMap.put(rltId, 1L);
        } else {
            uniqueCodeMaxVersionMap.put(rltId, uniqueCodeMaxVersionMap.get(rltId) + 1L);
        }
        return uniqueCodeMaxVersionMap.get(rltId);
    }

    /**
     * 移除未变更的历史数据
     *
     * @param rlts
     * @param
     */
    private void removeNochangeRltHis(List<ESCIRltInfoHistory> rlts) {
        Set<Long> rltIds = rlts.stream().collect(Collectors.groupingBy(ESCIRltInfoHistory::getRltId)).keySet();
        Page<ESCIRltInfoHistory> page = getListByQuery(1, 1, QueryBuilders.boolQuery());
        if (!BinaryUtils.isEmpty(page.getData())) {
            // 按照rltId取max折叠查
            List<ESCIRltInfoHistory> maxHiss = getListNoRepeat(rltIds.size(),
                    QueryBuilders.termsQuery("rltId", rltIds), "rltId", "version", false);
            if (maxHiss != null && maxHiss.size() > 0) {
                Map<Long, List<ESCIRltInfoHistory>> rltIdMaxHisMap = maxHiss.stream()
                        .collect(Collectors.groupingBy(ESCIRltInfoHistory::getRltId));
                rlts.removeIf(readRltHis -> {
                    Long rltId = readRltHis.getRltId();
                    if (rltIdMaxHisMap.containsKey(rltId)) {
                        ESCIRltInfoHistory rltMaxHis = rltIdMaxHisMap.get(rltId).get(0);
                        return readRltHis.equalsInfo(rltMaxHis);
                    }
                    return false;
                });
            }
        }
    }

    /**
     * 批量获取关系历史版本根据UnCode及version
     * @param rltUniqueCodeMap
     * @return
     */
    public List<ESCIRltInfoHistory> bathGetCIRltInfoHistoryByCIVersion(Map<String, Long> rltUniqueCodeMap) {
        if (CollectionUtils.isEmpty(rltUniqueCodeMap)) {
            return Collections.emptyList();
        }
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        for (Map.Entry<String, Long> entry : rltUniqueCodeMap.entrySet()) {
            if (BinaryUtils.isEmpty(entry.getKey())||BinaryUtils.isEmpty(entry.getValue())) {
                continue;
            }
            BoolQueryBuilder query1 = QueryBuilders.boolQuery();
            query1.must(QueryBuilders.termQuery("uniqueCode.keyword", entry.getKey()));
            query1.must(QueryBuilders.termQuery("version", entry.getValue()));
            query.should(query1);
        }
        return  super.getListByQuery(query);
    }


    /**
     * 动作类型
     *
     * @author zhaoyujie
     *
     */
    public static enum ActionType {
        SAVE_OR_UPDATE(0), DELETE(1);

        private int name;

        private ActionType(int name) {
            this.name = name;
        }

        public int getName() {
            return name;
        }

        @SuppressWarnings("unused")
        public void setName(int name) {
            this.name = name;
        }
    }


    /**
     *  根据uniqueCode查询关系历史库的最大版本
     * @param uniqueCodes
     *
     */
    public Map<String, Long> getUniqueCodeMaxVersion(List<String> uniqueCodes) {
        Map<String, Long> res = new HashMap<>();
        BoolQueryBuilder query = QueryBuilders.boolQuery();
        if(!BinaryUtils.isEmpty(uniqueCodes)){
            query.must(QueryBuilders.termsQuery("uniqueCode.keyword", uniqueCodes));
        }
        Map<String, BigDecimal> rltIdMaxVersionMap = super.groupByFieldMaxVal("uniqueCode.keyword", "version", query);
        if (rltIdMaxVersionMap != null && rltIdMaxVersionMap.size() > 0) {
            rltIdMaxVersionMap.forEach((key, val) -> res.put(key, val.longValue()));
        }
        return res;
    }
}
